/**
 * 1.synchronized底层
 *      1.修饰
 *          1.修饰实例方法    锁方法,看方法中的ACC_SYNCHRONIZED标志位，有的话也会隐式调用Monitor对象指令
 *          2.修饰静态方法    锁对象
 *          3.修饰代码块      锁代码块，字节码指令中会在加锁的前后加上monitorenter和monitorexit的指令，有多个锁就有多个monitor指令，会有个程序计数器计算锁的个数 +1 或者 -1
 *      2.对象头
 *          1.Mark Word，存储锁的标志位，关联一个monitor对象，实际拿锁就是对这个Monitor对象的争夺，CAS控制，争夺了就标志位变为1
 *          2.monitor对象是c++写的，
 *      3.锁升级技术：
 *          1.无锁
 *          2.偏向锁：monitor对象中记录一个线程id
 *          3.轻量级锁（自旋锁），自旋有次数限制，超过次数进化成重量级锁
 *          4.重量级锁
 *      4.如果抛出异常了：
 *          1.无论是在可重入里面还是直接上锁抛出异常，都是直接释放锁的，所有的可重入锁
 * 2.synchronized和lock区别：
 *      1.关键字和接口
 *      2.lock可中断，可尝试获取锁
 *      3.lock灵活
 *      4.自动释放锁和手动释放锁
 *      5.非公平和可以设置是否公平
 *      6.能锁方法和代码块和只能代码块
 * 3.concurrentHashMap
 *      1.1.7以前
 *          1.分段锁技术，底层数组+链表，hashEntry,segment，对segment加锁不会影响其他segment
 *          2.put先去hash得到segment下标，在hash得到hashEntry的下标，插入
 *          3.锁：Segment分段锁
 *      2.1.8
 *          1.synronized + cas 获取锁，颗粒度降低
 *          2.syncronzed锁升级技术
 *          3.数组 + 链表 + 红黑树
 * 4.Object下有哪些方法：hashCode,equel,notify,notifyall,clone,toSting,wait,finalliz
 * 5.CountDownLatch,CyclicBarrier,Semaphore
 *      1.CountDownLatch：线程计数器，有countDown和await两个方法，await是等到线程计数器为0时才可以继续运行
 *      2.CycliBarrier：当一定数量的线程准备就绪时，才可以运行，否则一直等待，构造方法传入线程数量和runnable，await方法等待，
 *        如果初始线程为2，有四个线程需要执行，则会分批次执行
 *      3.Semaphore：信号量，用来控制同一时间资源可被访问的线程数量，一般用于流量的控制
 *        初始化线程数  acquire()方法许可   release()方法释放许可
 * 6.ReentrantLock源码
 *      1.lock()
 *          1.首先用CAS去获取AQS中定义的一个变量state
 *              1.值设置成了1代表获取锁成功，设置当前的独占线程
 *              2.获取失败，则进入一个方法
 *                  1.tryAcquire，尝试获取锁，如果当前state为0则再获取一次，如果当前线程已经占用这个锁，则state + 1，这是重入性的保证
 *                  2.尝试失败，则将当前线程节点加入同步等待队列中，先用CAS去添加末尾节点，如果head为null，则执行enq方法加入
 *                  3.已经入队的线程尝试获取锁，自旋保证获取成功
 *                  4.获取第二个节点，再尝试获取锁，获取成功则设置为头节点，原先的头节点设置null等待GC
 *                  5.尝试失败则判断是否可以挂起，这个判断挂起是判断node节点中的waitStatus状态
 *                  6.> 0 的话，如果为SIGNAL则挂起，CANCEL则取消，为0的话则设置为SIGNAL
 *                  7.SIGNAL的作用是表示自己可以被唤醒
 *          2.公平锁和非公平锁的区别：
 *              1.在tryAcquire的方法里面，判断条件多了一个判断当前节点是否有前驱节点的
 *              2.如果有前驱节点，则代表还有其它线程在获取锁，则公平锁会放弃当前节点获取，而非公平锁继续获取
 *          3.condition
 *              1.与wait和notify的区别
 *                  1.Condition能够支持不响应中断，而通过使用Object方式不支
 *                  2.Condition能够支持多个等待队列（new 多个Condition对象），而Object方式只能支持一个
 *                  3.Condition能够支持超时时间的设置，而Object不支持
 *              2.lock是同步等待队列，condition可以有多个，是条件等待队列，如果调用了await方法，则addConditionWaiter进入到条件等待队列中等待
 *              3.当调用signal的时候，就会将节点移动到同步等待队列中，等待获取锁成功
 *              4.waitStatus中有个状态是condition
 *          final void lock() {
 *             首先用一个CAS去获取AQS中定义的一个变量state，尝试获取锁，状态为1就是获取到了锁
 *             值变为1且设置了当前线程为独占线程，就代表获取锁成功
 *             if (compareAndSetState(0, 1))
 *                 setExclusiveOwnerThread(Thread.currentThread());
 *             else
 *                 acquire(1);
 *          }
 *
 *          public final void acquire(int arg) {
 *             尝试获取锁，如果不成功则将当前线程节点加入同步等待队列中，
 *             if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 *             selfInterrupt();
 *          }
 *
 *          尝试获取非公平锁代码
 *          final boolean nonfairTryAcquire(int acquires) {
 *             final Thread current = Thread.currentThread();
 *             int c = getState();
 *             if (c == 0) { 如果没有线程占用锁，则再用CAS获取
 *                 if (compareAndSetState(0, acquires)) {
 *                     setExclusiveOwnerThread(current);
 *                     return true;
 *                 }
 *             }
 *             当前线程已经占用了锁，开始可重入性，更新state + 1，下次解锁需要解两次
 *             else if (current == getExclusiveOwnerThread()) {
 *                 int nextc = c + acquires;
 *                 if (nextc < 0) // overflow
 *                     throw new Error("Maximum lock count exceeded");
 *                 setState(nextc);
 *                 return true;
 *             }
 *             return false;
 *         }
 *
 *         获取不到锁加入同步等待队列代码，用CAS将节点加入队列，CAS会失败，就用enq
 *         private Node addWaiter(Node mode) {
 *              Node node = new Node(Thread.currentThread(), mode);
 *              Node pred = tail;
 *              if (pred != null) {
 *                  node.prev = pred;
 *                  if (compareAndSetTail(pred, node)) {
 *                      pred.next = node;
 *                      return node;
 *                  }
 *              }
 *              enq(node);
 *              return node;
 *          }
 *
 *          已经入队的线程尝试获取锁
 *          final boolean acquireQueued(final Node node, int arg) {
 *              boolean failed = true;标记是否成功获取到了锁
 *              try {
 *                  boolean interrupted = false;标记线程是否中断
 *                  for (;;) {自旋不断尝试
 *                      final Node p = node.predecessor();获取前驱节点
 *                      if (p == head && tryAcquire(arg)) {如果前驱节点是老二，证明有资格获取锁
 *                          setHead(node);获取成功则将该节点设置为头节点
 *                          p.next = null; 等待GC回收
 *                          failed = false;
 *                          return interrupted;
 *                      }
 *                      判断失败了是否可以挂起
 *                      if (shouldParkAfterFailedAcquire(p, node) &&
 *                          parkAndCheckInterrupt())
 *                          interrupted = true;
 *                  }
 *              } finally {
 *                  if (failed)
 *                 cancelAcquire(node);
 *              }
 *          }
 *
 *          挂起操作
 *          private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 *              判断node节点中waitStatus的状态，如果>0即为SIGNAL,CANCEL,CONDITION,如果为SIGNAL则挂起，如果为0初始状态，则设置为SIGNAL
 *              int ws = pred.waitStatus;
 *              if (ws == Node.SIGNAL) return true;
 *              if (ws > 0) {
 *                  do {
 *                      node.prev = pred = pred.prev;
 *                  } while (pred.waitStatus > 0);
 *                      pred.next = node;
 *              } else {
 *                  compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
 *              }
 *      2.unlock()
 *          1.执行释放锁方法，这个方法定义在AQS中，然后子类重写了tryRelease方法
 *          2.尝试释放锁，判断state的值，可重入性的保证，每次释放减1
 *          3.当state = 0时候，设置可以释放锁，线程独占设置为null，并且释放许可
 *
 *
 *          public void unlock() {
 *              sync.release(1);执行release方法，在AQS中定义的
 *          }
 *
 *          public final boolean release(int arg) {
 *              if (tryRelease(arg)) {  尝试释放锁
 *                  Node h = head;
 *                  if (h != null && h.waitStatus != 0)
 *                      unparkSuccessor(h); 尝试释放许可
 *                  return true;
 *              }
 *              return false;
 *          }
 *
 *          protected final boolean tryRelease(int releases) {
 *             int c = getState() - releases;  可重入性的保证，如果是reentrantLock则每次只能释放一把锁
 *             if (Thread.currentThread() != getExclusiveOwnerThread())
 *                 throw new IllegalMonitorStateException();
 *             boolean free = false;是否释放锁的标志位
 *             if (c == 0) {
 *                 free = true;
 *                 setExclusiveOwnerThread(null);释放独占线程
 *             }
 *             setState(c);
 *             return free;
 *         }
 *      3.tryLock()
 *          1.没定义超时时间，则走跟原来锁相同的方法
 *          2.定义了超时时间，则走另一个方法，求出一个deadTime过期时间，然后不断减去当前时间，小于0则跳出获取锁，返回获取失败
 *       4.doAcquireInterruptibly（）
 *          1.检测到中断就自动退出，并且抛出异常
 *          2.平时的话检测到中断，只会记录状态，后面再不断重试获取锁
 *
 * 7.CountDownLatch
 *      1.总结
 *          1.实例化new CountDownLatch(n)传入n，代表n个许可，需要获取n个许可的锁才能解锁，state = n
 *          2.调用countDown ，tryReleaseShared方法，每次释放一个锁
 *          3.调用await()方法，将等待的线程放入同步队列中，在自旋中不断尝试释放锁的许可
 *          4.当 state = 0时候，doReleaseShared()，释放共享锁，从同步队列中将节点退出
 *      2.await()
 *          public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
 *              if (Thread.interrupted())
 *                  throw new InterruptedException(); 首先判断是否被中断，中断就抛出异常
 *              if (tryAcquireShared(arg) < 0)
 *                  doAcquireSharedInterruptibly(arg);
 *          }
 *          判断state是否为0，当为0的时候，条件才达到，不为0则继续等待条件达到
 *          protected int tryAcquireShared(int acquires) {
 *             return (getState() == 0) ? 1 : -1;
 *          }
 *
 *          private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
 *              final Node node = addWaiter(Node.SHARED);
 *              boolean failed = true;
 *              try {
 *                  for (;;) {
 *                      final Node p = node.predecessor();
 *                      if (p == head) {
 *                          int r = tryAcquireShared(arg);
 *                          if (r >= 0) {
 *                              setHeadAndPropagate(node, r);
 *                              p.next = null; // help GC
 *                              failed = false;
 *                              return;
 *                          }
 *                      }
 *                      if (shouldParkAfterFailedAcquire(p, node) &&
 *                          parkAndCheckInterrupt())
 *                          throw new InterruptedException();
 *                  }
 *              } finally {
 *                  if (failed)
 *                      cancelAcquire(node);
 *              }
 *          }
 *
 *          private void setHeadAndPropagate(Node node, int propagate) {
 *              Node h = head; // 记录头结点
 *              setHead(node);//设置node为头结点
 *              if (propagate > 0 || h == null || h.waitStatus < 0) {
 *                  Node s = node.next;//获得当前节点的下一个节点，如果为最后一个节点或者，为shared
 *                  if (s == null || s.isShared())
 *                 doReleaseShared();//释放共享锁
 *              }
 *          }
 *
 *          private void doReleaseShared() {
 *              for (;;) {
 *                  Node h = head;//获得头结点
 *                  if (h != null && h != tail) {
 *                      int ws = h.waitStatus;//获取头结点的状态默认值为0
 *                      if (ws == Node.SIGNAL) {如果等于SIGNAL唤醒状态
 *                 	        //将头结点的状态置成0，并使用Node.SIGNAL(-1)与0比较,continue，h的状态设置为0,不会再进入if (ws == Node.SIGNAL)
 *                          if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
 *                              continue;            // loop to recheck cases
 *                          unparkSuccessor(h);
 *                      }//判断ws是否为0，并且h的状态不等于0，这里是个坑啊，ws等于0，h就是0啊,所以if进不来的，并设置节点为PROPAGATE
 *                      else if (ws == 0 &&
 *                                  !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
 *                          continue;                // loop on failed CAS
 *                  }
 *                  如果头结点等于h,其实也没有一直loop，由于上面写的Node h = head,就算前面的条件都不满足，这里一定会break
 *                  if (h == head)                   // loop if head changed
 *                      break;
 *              }
 *          }
 * 8.AbstractQueuedSynchronizer
 *      1.基础知识
 *          1.基于许可的多线程控制：为了控制多线程访问共享资源，需要为每个共享区间的线程派发一个许可，拥有许可才能进入共享区间活动。
 *            离开共享区间必须归还许可，确保后续线程可以正常获取许可。如果许可用完，线程必须等待，这就是控制多线程并行的思想
 *          2.排他锁和共享锁：排他模式下只有一个线程可以访问共享变量，共享模式允许多个线程访问。可重入锁是排他的，信号量是共享的
 *          3.LockSupport：pack()如果没有可用许可，则挂起线程，unpark(Thread thread)给thread一个可用许可，继续执行。
 *            park在有许可的情况下是不会阻塞线程的。AQS用LockSupport的park()和unPark()操作来控制线程的运行状态的
 *      2.内部数据结构：
 *          1.同步等待队列：保存等待在这个锁上的线程（由于Lock()操作引起的等待）。
 *          2.条件变量等待队列：为了维护等待在条件变量上的等待线程，AQS又维护了一个条件变量等待队列（由于Condition.await()引起的阻塞的线程）
 *            一个可重入锁可以生成多个条件变量对象，因为AQS内部可能维护多个条件变量等待队列 ConditionObject
 *          3.同步等待队列和条件变量等待队列都用同一个node类作为链表的节点，包含prev，next，thread（线程对象），条件变量等待队列还使用nextWaiter
 *            表示下一个等待的节点。Node节点还有waitStatus，表示节点等待在队列中的状态
 *              1.CANCELLED:表示线程取消了等待
 *              2.SIGNAL:表示后续节点需要被唤醒
 *              3.CONDITION:表示线程等待在条件变量队列中
 *              4.0:初始状态
 *          4.排他锁（reentreent lock）
 *                  获取请求许可
 *                  public final void acquire(int arg) {
 *                      尝试获取许可，如果失败，则先使用addWaiter()将当前线程加入同步等待队列，然后继续获取锁
 *                      if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 *                          selfInterrupt();
 *                  }
 *                  private Node addWaiter(Node mode) {
 *                      Node node = new Node(Thread.currentThread(), mode);
 *                      将节点加入队列尾端，这是一个快速的方法，可能失败，如果快速加入失败，旧用enq函数加入队列末尾
 *                      Node pred = tail;
 *                      if (pred != null){
 *                          node.prev = pred;
 *                          if (compareAndSetTail(pred, node)) {
 *                              pred.next = node;
 *                              return node;
 *                          }
 *                      }
 *                      enq(node);
 *                      return node;
 *                  }
 *                  为已经在队列中的Node请求一个许可
 *                  final boolean acquireQueued(final Node node, int arg){
 *                      boolean failed = true;
 *                      try {
 *                          boolean interrupted = false;
 *                          for (;;) {
 *                              final Node p = node.predecessor();
 *                                  只有队列中的第二个节点才能尝试，因为第一个节点已经在运行了，请求锁已经成功，队列中的第二个节点其实就是最早的那个请求者
 *                                  if (p == head && tryAcquire(arg)) {
 *                                  如果获取锁成功则把自己设置成头部
 *                                  setHead(node);
 *                                  p.next = null; // help GC
 *                                  failed = false;
 *                                  return interrupted;
 *                          }
 *                          请求锁失败了，判断线程waitStatus，如果是SIGNAL则需要park()，挂起线程，对于CANCEL的节点，则删除，对于初始节点，则设置成SIGNAL
 *                          if (shouldParkAfterFailedAcquire(p, node) &&
 *                              parkAndCheckInterrupt())
 *                              interrupted = true;
 *                          }
 *                      }finally {
 *                          if (failed){
 *                              如果意外失败了，比如异常之类的，那么就取消这个请求
 *                              cancelAcquire(node);
 *                          }
 *                      }
 *                  }
 *
 *                  Condition.await()，线程进入等待
 *                  public final void await() throws InterruptedException {
 *                      if (Thread.interrupted())
 *                          throw new InterruptedException();
 *                      添加一个节点到条件变量等待队列
 *                      Node node = addConditionWaiter();
 *                      进入等待前一定要释放已经持有的许可
 *                      int savedState = fullyRelease(node);
 *                      int interruptMode = 0;
 *                      当前节点是不是已经在同步队列中
 *                      while (!isOnSyncQueue(node)) {
 *                          当前节点不在同步队列中则直接挂起
 *                          LockSupport.park(this);
 *                          if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
 *                              break;
 *                      }
 *                      到这表示已经被唤醒并且在同步队列中了，直接用acquireQueued（）再次请求许可
 *                      从await()唤醒的线程必须再获得许可才行，释放几个许可就要获取几个许可
 *                      if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
 *                          interruptMode = REINTERRUPT;
 *                      if (node.nextWaiter != null) // clean up if cancelled
 *                          unlinkCancelledWaiters();
 *                      if (interruptMode != 0)
 *                          reportInterruptAfterWait(interruptMode);
 *                  }
 *
 *                  public final void signal() {
 *                      if (!isHeldExclusively())
 *                          throw new IllegalMonitorStateException();
 *                      从第一个节点开始
 *                      Node first = firstWaiter;
 *                      if (first != null)
 *                          doSignal(first);
 *                  }
 *
 *                  private void doSignal(Node first) {
 *                      do {
 *                          if ( (firstWaiter = first.nextWaiter) == null)
 *                          lastWaiter = null;
 *                          first.nextWaiter = null;
 *                          transferForSignal()把条件等待队列中的元素，移动到同步等待队列尾端
 *                      } while (!transferForSignal(first) && (first = firstWaiter) != null);
 *                  }
 *
 *                  释放排他锁
 *                  public final boolean release(int arg) {
 *                      tryRelease(arg)和tryAcquire一样在子类有具体实现
 *                      if (tryRelease(arg)) {
 *                          Node h = head;
 *                          if (h != null && h.waitStatus != 0)
 *                              从队列中唤醒一个等待中的线程（遇到CANCEL直接跳过）
 *                              unparkSuccessor(h);
 *                          return true;
 *                      }
 *                      return false;
 *                  }
 *
 *          2.共享锁：
 *              1.acquireShared(int arg)：tryAcquireShared(int arg)尝试获取共享锁，申请失败进入同步等待队列doAcquireShared()
 *              2.doAcquireShared：第二个节点尝试申请许可，tryAcquireShared，申请成功把自己设置为头部，并且根据条件判断是否唤醒后续线程，条件允许则唤醒后续节点
 *              3.没机会申请许可则park()，下次继续尝试步骤2
 *              4.释放共享锁：tryReleaseShared()尝试释放许可，doReleaseShared()，尝试释放许可成功后，就可以唤醒线程，设置传播态，唤醒后续节点了
 * 9.volatile底层
 *      1. JMM:java内存模式。定义了所有的变量都存储在主内存中，每个线程都有自己的私有内存，想要用到这些主内存中的变量，必须从主内存中拷贝一份到线程内存中。
 *         对变量的操作都必须在私有内存中进行，不同线程之间也不能访问对方的变量，只能通过主内存进行值的传递与交换。JMM需要保证原子性，可见性，有序性
 *         1.可见性：某个线程改动了一个变量，其他线程能立刻感知到这个改动的变量的值
 *              1.解决方案
 *                  1.synchronized加锁，线程会先获取锁，然后清空工作内存，同步主内存中的变量，执行代码，将修改后的变量重新写入主内存
 *                  2.volatile修饰，volatile保证不同线程对共享变量操作的可见性，一个线程修改了volatile修饰的变量，当修改写回主内存时，
 *                    另一个线程能立即看到最新的值，怎么做到的呢？
 *                      1.MESI缓存一致性协议：CPU写数据时，如果发现变量是共享变量，则会发出信号通知其它CPU将改变量的缓存行设置为无效状态，
 *                        因此当其它CPU需要读取改变量时，发现自己的缓存行无效，就会重新去内存读取
 *                      2.嗅探：每个CPU通过嗅探在总线上传递的数据来知道自己的缓存是否过期了，当发现过期则设置缓存行无效
 *                      3.总线风暴：由于volatile的缓存一致性协议需要不断从主内存中嗅探缓存是否失效，会导致总线带宽达到峰值
 *         2.有序性：单线程中，代码的执行顺序都是从前往后的，多线程中，有可能出现乱序，可能出现指令重排和主内存工作内存同步延迟的现象
 *              1.解决方案：
 *                  1.volatile
 *                      1.指令重排序：为了提高性能，编译器和处理器常常把代码的指令进行重排序
 *                          1.编译器优化重排序
 *                          2.指令集并行重排序
 *                          3.内存系统的重排序
 *                      2.as-if-serial：不管怎么排序，执行结果不能变化
 *                      3.内存屏障
 *                          1.java编译器在生成指令重排序的时候在适当位置会加入内存屏障指令来禁止特定类型的处理器重排序
 *                          2.volatile写是在前后插入内存屏障，读是在后面插入两个内存屏障
 *                      4.happens-before
 *                          1.如果一个操作执行的结果需对另一个操作可见，那么这两个操作之间必须存在happens-before
 *         3.原子性：一个操作要么全部执行成功，要么不执行，有点像事务
 * 10.CAS原理
 *      1.比较并交换，CAS在读取数据的时候不加锁，在准备写入数据的时候，先去查询原值，操作的时候比较原值是否被修改，若没被修改则写回，否则重新读取
 *      2.问题：
 *          1.没修改成功就循环，CPU开销是个问题
 *          2.ABA问题
 *              1.解决：加版本号 update table set value = newvalue,version = version + 1 where version = #{version} and value = #{value}
 *          3.单对象访问
 * 11.threadLocal
 *      1.ThreadLocalMap由一个个Entry对象构成
 *      2.Entry的key是ThreadLocal对象，是一个弱引用，会被垃圾回收器回收
 *      3.set时，会获取当前线程对象，再获取threadLocalMap对象，再以当前threadLocal对象为Key，将值存储进threadLocalMap对象中
 *      4.每个线程都有自己的threadLocalMap对象，所以线程私有
 *      5.内存泄漏问题
 *          1.强引用：使用最普遍的引用（new对象），不会被垃圾回收，当内存空间不足，java虚拟机宁愿抛出oom也不会回收
 *          2.弱引用：JVM进行垃圾回收的时候，无论内存是否充足，都会回收被弱引用关联的对象
 *          3.threadLocalMap的key为弱引用，key为threadLocal对象
 *          4.threadLocal对象被回收了，则导致threadLocalMap的key为null，而value为强引用
 *          5.如果线程一直不中断，则key为null的Entry就会一直存在\
 *          6.为什么threadlocal不适用强引用:使用强引用key不会被回收，导致内存泄漏
 *          7.使用弱引用的话，在threadLocalMap的set,get,remove方法执行的时候会清掉Key为null的数据
 *          8.threadLocal内存泄漏的根源是：由于threadLocalMap的生命周期和thread一样长，如果没有手动删除对应的key就会导致内存泄漏，而不是因为弱引用
 *          9.正确使用方法:
 *              1.每次用完threadlocal都调用它的remove()方法
 *              2.将threadLocal变量定义成private static，这样就一直存在threadLocal的强引用，也能保证在任何时候都能通过threadlocal的弱引用访问到Entry的value
 * 12.多线程环境下为什么要引入同步的机制
 *      1.先讲一下JMM内存模型
 *      2.保证对共享资源的访问不会出现问题，举个修改内存变量的例子
 * 13.乐观锁和悲观锁
 *      1.悲观锁：悲观锁认为，对一个数据的并发操作一定是会发生数据的修改的，哪怕没修改也会认为会修改，所以一定会加上锁，不加上锁就会出现问题
 *      2.乐观锁：乐观锁认为，获取数据的时候并不担心数据会被修改，每次获取数据也不加锁，只是在更新数据的时候，通过判断现有数据是否和原数据一致来
 *        判断数据是否被其它线程操作了，如果没被其它线程更新，则自己更新数据
 * 14.sleep和wait有什么区别
 *      1.sleep方法属于Thread类，wait方法属于Object类
 *      2.sleep方法暂停执行指定的时间，让出CPU给其他线程，但其监控状态依然保持在指定的时间过后又会自动恢复运行状态
 *      3.在调用sleep方法的过程中，线程不会释放对象锁，而wait会释放对象锁
 * 15.wait为什么是数Object类下面的方法
 *      1.所谓的释放锁资源实际是通知对象内置的monitor对象进行释放，而只有所有对象都有内置的monitor对象才能实现任何对象的锁资源都可以释放，
 *        又因为所有类都继承自Object，所以wait(）就成了Object方法，也就是通过wait()来通知对象内置的monitor对象释放，
 *        而且事实上因为这涉及对硬件底层的操作，所以wait()方法是native方法，底层是用C写的
 *
 *
 * @author
 * @created 2022/1/6 21:22
 */
public class JavaSync {

}
